home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / flockPiczoService.js < prev    next >
Text File  |  2007-10-12  |  64KB  |  1,939 lines

  1. // vim: ts=2 sw=2 expandtab cindent
  2. //
  3. // BEGIN FLOCK GPL
  4. // 
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. // 
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. // 
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. // 
  17. // END FLOCK GPL
  18. //
  19.  
  20. const Cc = Components.classes;
  21. const Ci = Components.interfaces;
  22. const Cr = Components.results;
  23. const Cu = Components.utils;
  24.  
  25. const CLASS_ID = Components.ID("{8DAEC4A0-8623-11DB-B606-0800200C9A66}");
  26. const CLASS_NAME = "piczo";
  27. const CLASS_DESC = "Flock Piczo Service";
  28. const CONTRACT_ID = "@flock.com/people/piczo;1";
  29. const FLOCK_PHOTO_ALBUM_CONTRACTID = "@flock.com/photo-album;1";
  30. const FLOCK_PHOTO_ACCOUNT_CONTRACTID = "@flock.com/photo-account;1";
  31. const SERVICE_ENABLED_PREF = "flock.service.piczo.enabled";
  32. const CATEGORY_COMPONENT_NAME       = "Piczo JS Component"
  33. const CATEGORY_ENTRY_NAME           = "piczo"
  34.  
  35. var gCompTK;
  36. function getCompTK() {
  37.   if (!gCompTK) {
  38.     gCompTK = Components.classes["@flock.com/singleton;1"]
  39.                         .getService(Components.interfaces.flockISingleton)
  40.                         .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
  41.                         .wrappedJSObject;
  42.   }
  43.   return gCompTK;
  44. }
  45.  
  46. var gTimers = [];  // For use with the scheduler
  47.  
  48. // String defaults... may be updated later through Web Detective
  49. var gStrings = {
  50.   "domains": "piczo.com",
  51.   "sessioncookie": "JSESSIONID",
  52.   "mainpage": "http://www.piczo.com/",
  53.   "favicon": "http://www.piczo.com/favicon.ico",
  54.   "userlogin": "http://www.piczo.com/",
  55.   "userprofile": "http://%accountid%.piczo.com/",
  56.   "api-agent-string": "Flock",
  57.   "api-setup-URL": "http://api.piczo.com/api-1/setup?api-agent=%agent%",
  58.   "api-login-URL": "http://api.piczo.com/api-1/login",
  59.   "api-friends-URL": "http://api.piczo.com/api-1/friends",
  60. };
  61.  
  62. var gAPI = null;
  63. var gSVC = null;
  64.  
  65. function loadLibraryFromSpec(aSpec)
  66. {
  67.   var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  68.                          .getService(Components.interfaces.mozIJSSubScriptLoader);
  69.   loader.loadSubScript(aSpec);
  70. }
  71.  
  72. loadLibraryFromSpec("chrome://browser/content/flock/common/flocksafe.js");
  73. loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
  74.  
  75. function hex_md5_stream(stream)
  76. {
  77.     var hasher = Components.classes["@mozilla.org/security/hash;1"]
  78.         .createInstance(Components.interfaces.nsICryptoHash);
  79.     hasher.init(hasher.MD5);
  80.  
  81.     hasher.updateFromStream(stream, stream.available());
  82.     var hash = hasher.finish(false);
  83.  
  84.     var ret = '';
  85.     for (var i = 0; i < hash.length; ++i) {
  86.         var hexChar = hash.charCodeAt(i).toString(16);
  87.         if (hexChar.length == 1)
  88.             ret += '0';
  89.         ret += hexChar;
  90.     }
  91.  
  92.     return ret;
  93. }
  94.  
  95. function hex_md5(s)
  96. {
  97.     var stream = Components.classes["@mozilla.org/io/string-input-stream;1"]
  98.         .createInstance(Components.interfaces.nsIStringInputStream);
  99.     stream.setData(s, s.length);
  100.  
  101.     return hex_md5_stream(stream);
  102. }
  103. // ==============================================
  104. // ========== BEGIN piczoService class ==========
  105. // ==============================================
  106. var _logger = null;
  107. function piczoService()
  108. {
  109.   _logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  110.   _logger.init("piczo");
  111.   _logger.info("CONSTRUCTOR");
  112.  
  113.   this.obs = Components.classes["@mozilla.org/observer-service;1"]
  114.                        .getService(Components.interfaces.nsIObserverService);
  115.   this.acUtils = Components.classes["@flock.com/account-utils;1"]
  116.                            .getService(Components.interfaces.flockIAccountUtils);
  117.   this.url = gStrings["mainpage"];
  118.   this.mIsInitialized = false;
  119.   this._ctk = {
  120.     interfaces: [
  121.       "nsISupports",
  122.       "nsIClassInfo",
  123.       "nsISupportsCString",
  124.       "nsIObserver",
  125.       "flockIWebService",
  126.       "flockISocialWebService",
  127.       "flockIManageableWebService",
  128.       "flockIPollingService",
  129.       "flockIAuthenticateNewAccount",
  130.       "flockIMediaWebService",
  131.       "flockIMediaUploadAccount", 
  132.       "flockIPhotoAPI",
  133.       "flockIPiczoService"
  134.     ],
  135.     shortName: "piczo",
  136.     fullName: "Piczo",
  137.     description: CLASS_DESC,
  138.     domains: gStrings["domains"],
  139.     favicon: gStrings["favicon"],
  140.     CID: CLASS_ID,
  141.     contractID: CONTRACT_ID,
  142.     accountClass: piczoAccount,
  143.   };
  144. }
  145.  
  146. // BEGIN flockIPhotoAPI interface
  147. function createEnum( array ) {
  148.   return {
  149.     QueryInterface: function (iid) {
  150.       if (iid.equals(Ci.nsISimpleEnumerator)) {
  151.         return this;
  152.       }
  153.       throw Cr.NS_ERROR_NO_INTERFACE;
  154.     },
  155.     hasMoreElements: function() {
  156.       return (array.length>0);
  157.     },
  158.     getNext: function() {
  159.       return array.shift();
  160.     }
  161.   }
  162. }
  163.  
  164. function piczoPhoto() {
  165.  
  166. }
  167.  
  168. piczoPhoto.prototype= {
  169.   id: "",
  170.   thumbnail: "",
  171.   webPageUrl: "",
  172.   midSizePhoto: "",
  173.   largeSizePhoto: "",
  174.   title: "",
  175.   username: "",
  176.   userid: "",
  177.   is_public: true,
  178.   is_video: false,
  179.   svcShortName: 'piczo',
  180.   buildTooltip: function( ) {
  181.     // do we have to use document from the window to ceate elements? -- ja
  182.     var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
  183.              getService(Ci.nsIWindowMediator);
  184.     var win = wm.getMostRecentWindow('navigator:browser');
  185.     if (!win) {
  186.       return null;
  187.     }
  188.  
  189.     var hbox = win.document.createElement('hbox');
  190.     var img = win.document.createElement('image');
  191.     img.setAttribute('src', this.icon );
  192.     hbox.appendChild(img);
  193.     var box = win.document.createElement('vbox');
  194.     box.setAttribute('style', 'max-width: 250px');
  195.     var title = win.document.createElement('label');
  196.     title.setAttribute('value', this.title );
  197.     title.setAttribute('crop', 'end');
  198.     box.appendChild(title);
  199.     var lbl = win.document.createElement('label');
  200.     lbl.setAttribute('value', this.username );
  201.     lbl.setAttribute('class', 'user');
  202.     box.appendChild(lbl);
  203.     hbox.appendChild(box)
  204.  
  205.     var vbox = win.document.createElement('vbox');
  206.     var cbox = win.document.createElement('hbox'); 
  207.     var largeImg = win.document.createElement('image');
  208.     largeImg.setAttribute('src', this.midSizePhoto);
  209.     largeImg.setAttribute('style', 'margin-bottom: 2px;');
  210.     var spacer = win.document.createElement('spacer');
  211.     spacer.setAttribute('flex', '1');
  212.     cbox.appendChild(largeImg);
  213.     cbox.appendChild(spacer);
  214.     vbox.appendChild(cbox);
  215.     vbox.appendChild(hbox);
  216.  
  217.     return vbox;
  218.   },
  219.   buildHTML: function( ) {
  220.     return '<a title="'+this.title+'" href="'+this.webPageUrl+'"><img src="'+this.midSizePhoto+'" border="0" /></a>';
  221.   },
  222.   buildLargeHTML: function( ) {
  223.     return '<a title="'+this.title+'" href="'+this.webPageUrl+'"><img src="'+this.largeSizePhoto+'" border="0" /></a>';
  224.   },
  225.   buildBBCode: function ( ) {
  226.     return '[url=' + this.webPageUrl + '][img]'+ this.largeSizePhoto +'[/img][/url]';
  227.   },
  228.   QueryInterface: function(iid) {
  229.     if (!iid.equals(Ci.nsISupports) && !iid.equals(Ci.flockIPhoto)) {
  230.       throw Cr.NS_ERROR_NO_INTERFACE;
  231.     }
  232.     return this;
  233.   }
  234. };
  235.  
  236. piczoService.prototype.iconUrl = gStrings["favicon"];
  237.  
  238. piczoService.prototype.needsAuthenticatedApi = true;
  239.  
  240. piczoService.prototype.handleDragDrop = 
  241. function piczoService_handleDragDrop (aURL, aXLoc, aYLoc) {
  242.   return gAPI.handleDragDrop(aURL, aXLoc, aYLoc);
  243. }
  244.  
  245. piczoService.prototype.supportsFeature = 
  246. function piczoService_supportsFeature(aFeature)
  247. {
  248.   var supports = {};
  249.   supports.tags = false;
  250.   supports.title = false;
  251.   supports.notes = false;
  252.   supports.fileName = false;
  253.   supports.contacts = true;
  254.   supports.privacy = false;
  255.   supports.albumCreation = false;
  256.   return (supports[aFeature] == true);
  257. }
  258.  
  259. piczoService.prototype.login =
  260. function piczoService_login(aAccountURN, aListener) {
  261.   _logger.info("piczoService.login()");
  262. }
  263.  
  264. piczoService.prototype.__defineGetter__('isLoggedIn', function () { return gAPI.isLoggedIn(); })
  265.  
  266. piczoService.prototype.getAuthPerson = function () {
  267. try {
  268.     var session = gAPI.getAuthUser();
  269.     var person = {};
  270.     person.id = session.userGUID;
  271.     person.fullname = session.username;
  272.     person.username = session.username;
  273.     person.service = this;
  274.     return person;
  275.   }
  276.   catch(e) {
  277.     return null;
  278.   }
  279. }
  280.  
  281. piczoService.prototype.findByUsername = 
  282. function piczoService_findByUsername(aListener, aUsername) {
  283.   // Needed to have observer call .search();
  284.   aListener.onError(null);
  285. }
  286.  
  287. piczoService.prototype.serviceName = "Piczo";
  288. piczoService.prototype.shortName = "piczo";
  289.  
  290. piczoService.prototype.search =
  291. function piczoService_search(aListener, aQueryString, aCount, aPage)
  292. {
  293.   // Piczo doesn't support paging
  294.   if(aPage > 1) return;
  295.  
  296.   // XXX: TODO
  297.   _logger.info("piczoService_search: " + aQueryString + "\n");
  298.  
  299.   var aQuery = new queryHelper(aQueryString);
  300.   var myListener = {
  301.     onSuccess: function (enumResults, status) {
  302.       aListener.onSearchResult(enumResults);
  303.     },
  304.     onError: function (aFlockError) {
  305.       //_logger.info("piczoService_search ERROR: " + aFlockError.serviceErrorString + "\n");
  306.       aListener.onError(aFlockError);
  307.     }
  308.   }
  309.  
  310.   var params = Components.classes["@mozilla.org/hash-property-bag;1"]
  311.                         .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  312.   params.setPropertyAsAString("subj_id", aQuery.search);
  313.   params.setPropertyAsAString("albumlabel", aQuery.albumlabel);
  314.   if (gAPI.isLoggedIn())
  315.   {
  316.     gAPI.getPhotos(myListener, params);
  317.   } else {
  318.     _logger.info("piczoService wasnt logged in....\n");
  319.     // We are not logged in, force user to log in now.
  320.     var aError = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  321.     aError.errorCode   = Ci.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
  322.     aListener.onError(aError);
  323.   }
  324. }
  325.  
  326. piczoService.prototype.cancelUpload =
  327. function piczoService_cancelUpload() {
  328.   try {
  329.     _logger.info("Cancelling upload...\n");
  330.     gAPI.uploader.req.abort();
  331.   } catch (e) {
  332.     _logger.info("Error cancelling upload.\n");
  333.   }
  334. }
  335.  
  336. piczoService.prototype.upload2 =
  337. function piczoService_upload2(aListener, aUpload, aFile) {
  338.   _logger.info("piczoService_upload2");
  339.  
  340.   var params = {};
  341.   /*params.title = aUpload.title;
  342.   params.description = aUpload.description;
  343.   params.is_family = aUpload.is_family;
  344.   params.is_friend = aUpload.is_friend;
  345.   params.is_public = aUpload.is_public;
  346.   params.async = "1";
  347.   params.tags = aUpload.tags;
  348.   */
  349.   gAPI.upload(aListener, aFile, params, aUpload);
  350. }
  351.  
  352. piczoService.prototype.getAlbums = 
  353. function piczoService_getAlbums(aListener, aUserID) {
  354. _logger.info("[piczoAPI].getAlbum call...\n");
  355.   var myListener = {
  356.     onResult: function (enumResults, status) {
  357.       _logger.info("[piczoAPI].getAlbums onResult!\n");
  358.       aListener.onGetAlbumsResult(enumResults);
  359.     },
  360.     onError: function (flockError) {
  361.       _logger.info("[piczoAPI].getAlbums onError!" + flockError.errorString + "\n");
  362.       //aListener.onError("Unable to get albums: " + flockError.errorString);
  363.     }
  364.   }
  365.  
  366.   // NOTE: the uid needs to be changed to the uid of aUsername for general user searches
  367.   gAPI.getAlbums(myListener, aUserID);
  368. }
  369.  
  370. piczoService.prototype.getAccountStatus = 
  371. function piczoService_getAccountStatus(aListener) {
  372.   var inst = this;
  373.   
  374.   var listener = {
  375.     onSuccess: function (aXML) {
  376.       _logger.info("piczoService: getAccountStatus Success!\n");
  377.       var photoAccount = Components.classes[FLOCK_PHOTO_ACCOUNT_CONTRACTID].createInstance(flockIPhotoAccount);
  378.       //var username = aXML.getElementsByTagName('username')[0].firstChild.nodeValue;
  379.       //var bandwidth = aXML.getElementsByTagName('bandwidth')[0];
  380.       //var maxFileSize = aXML.getElementsByTagName('filesize')[0];
  381.       //var isPro = aXML.getElementsByTagName('user')[0].getAttribute('ispro');
  382.       photoAccount.maxSpace = "500000";
  383.       photoAccount.usedSpace = "0";
  384.       photoAccount.maxFileSize = "500000";
  385.       photoAccount.usageUnits = "bytes";
  386.       photoAccount.isPremium = false;
  387.       aListener.onGetAccountStatus(photoAccount);
  388.     },
  389.     onError: function (aError) {
  390.       aListener.onError(aError);
  391.       _logger.info("piczoService: getAccountStatus ERROR\n");
  392.     }
  393.   }
  394.   
  395.   gAPI.getAccountStatus(listener);
  396. }
  397.  
  398. piczoService.prototype.call =
  399. function piczoService_call(aListener, aMethod, aDictionary, aAuth)
  400. {
  401.   _logger.info("piczoService_call\n");
  402.   return this.doCall(aListener, aMethod, aDictionary, aAuth);
  403. }
  404.  
  405. piczoService.prototype.createAlbum =
  406. function piczoService_createAlbum(aListener, aPath)
  407. {
  408.   // XXX: TODO  
  409.   throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  410. }
  411.  
  412. piczoService.prototype.supportsSearch =
  413. function piczoService_supportsSearch(aQueryString ) {
  414.   _logger.info("piczoService_supportsSearch: " + aQueryString + "\n");
  415.   return false;
  416. }
  417.  
  418.  
  419. // BEGIN flockIWebService interface
  420. piczoService.prototype.addAccountById =
  421. function piczoService_addAccountById(aAccountID, aIsTransient, aListener)
  422. {
  423.   _logger.info("{flockIWebService}.addAccountById('"+aAccountID+"', "+aIsTransient+")");
  424.   var accountURN = "urn:flock:piczo:account:"+aAccountID;
  425.   var c_acct = new this._coop.Account(
  426.     accountURN, {
  427.       name: aAccountID,
  428.       accountId: aAccountID,
  429.       serviceId: CONTRACT_ID,
  430.       service: this.c_svc,
  431.       favicon: gStrings["favicon"],
  432.       URL: gStrings["userprofile"].replace("%accountid%", aAccountID),
  433.       isTransient: aIsTransient,
  434.       isPollable: true,
  435.     }
  436.   );
  437.   this._coop.accounts_root.children.add(c_acct);
  438.   this.updateActions(accountURN);
  439.  
  440.   // Instanciate account component
  441.   var acct = this.getAccount(c_acct.id());
  442.   if (aListener) aListener.onSuccess(acct, "addAccount");
  443.   return acct;
  444. }
  445. // END flockIWebService interface
  446.  
  447.  
  448. // BEGIN flockISocialWebService interface
  449. piczoService.prototype.decorateForPerson =
  450. function piczoService_decorateForPerson(aDocument)
  451. {
  452.   _logger.info("{flockISocialWebService}.decorateForPerson()");
  453.   // XXX TODO FIXME: implement
  454. }
  455.  
  456. piczoService.prototype.browseFriends =
  457. function piczoService_browseFriends(aFriendURN, aListener)
  458. {
  459.   _logger.info("{flockISocialWebService}.browseFriends('"+aFriendURN+"')");
  460.   // XXX TODO FIXME: implement
  461. }
  462. // END flockISocialWebService interface
  463.  
  464. piczoService.prototype.authenticateNewAccount =
  465. function flickrService_authenticateNewAccount(aListener)
  466. {
  467.   _logger.info("{flockIAuthenticateNewAccount}.authenticateNewAccount(aListener)");
  468.   var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  469.   var win = wm.getMostRecentWindow('navigator:browser');
  470.   win.openUILinkIn(this.url, "tab");
  471. }
  472.  
  473.  
  474. // BEGIN flockIMediaWebService interface
  475. piczoService.prototype.decorateForMedia =
  476. function piczoService_decorateForMedia(aDocument)
  477. {
  478.   _logger.info("{flockIMediaWebService}.decorateForMedia()");
  479.   // XXX TODO FIXME: implement
  480. }
  481. // END flockIMediaWebService interface
  482.  
  483.  
  484. // BEGIN flockIManageableWebService interface
  485. piczoService.prototype.getAccountIDFromDocument =
  486. function piczoService_getAccountIDFromDocument(aDocument)
  487. {
  488.   _logger.info("{flockIManageableWebService}.getAccountIDFromDocument()");
  489.   aDocument.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  490.   var acctID;
  491.   var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  492.                           .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  493.   if (this.webDetective.detect("piczo", "accountinfo", aDocument, results)) {
  494.     // If this page has the user GUID on it, then we can do a Coop lookup
  495.     // for accounts with that GUID
  496.     acctID = this._getAccountIDFromGUID(results.getPropertyAsAString("piczo_guid"));
  497.   }
  498.   if (!acctID) {
  499.     // We haven't determined the account yet.
  500.     // Since some login landing pages don't have account info on them that we
  501.     // can detect, we can try seeing if there's a session cookie stored as a
  502.     // temp password entry.  (This is a hack.)
  503.     var sessionValue = this.acUtils.getCookie("http://piczo.com",
  504.                                               gStrings["sessioncookie"]);
  505.     if (!sessionValue) {
  506.       sessionValue = this.acUtils.getCookie("http://www.piczo.com",
  507.                                             gStrings["sessioncookie"]);
  508.     }
  509.     var pw = this.acUtils.getTempPassword("piczo:session:"+sessionValue);
  510.     if (pw) {
  511.       _logger.info("Sneaky! Got acctID from a temp password associated with session cookie!");
  512.       acctID = pw.user;
  513.     }
  514.   }
  515.   return acctID;
  516. }
  517.  
  518. piczoService.prototype.getCredentialsFromForm =
  519. function piczoService_getCredentialsFromForm(aForm)
  520. {
  521.   // Convenience method for Web Detective calls
  522.   var inst = this;
  523.   var detectForm = function piczo_detectForm(aType, aResults) {
  524.     return inst.webDetective.detectForm("piczo", aType, aForm, aResults);
  525.   };
  526.  
  527.   // Try to detect login, then signup, then changepassword, in that order
  528.   var formType = "login";
  529.   var results = getCompTK().newResults();
  530.   if (!detectForm(formType, results)) {
  531.     // No login detected, so check for signup
  532.     formType = "signup";
  533.     results = getCompTK().newResults();
  534.     if (!detectForm(formType, results)) {
  535.       // No signup detected, so try changepassword
  536.       formType = "changepassword";
  537.       results = getCompTK().newResults();
  538.       if (!detectForm(formType, results)) {
  539.         results = null;
  540.       }
  541.     }
  542.   }
  543.  
  544.   if (results) {
  545.     // We were able to get results from at least one of the checks above
  546.     var pw = {
  547.       QueryInterface: function(aIID) {
  548.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  549.             !aIID.equals(Components.interfaces.nsIPassword) &&
  550.             !aIID.equals(Components.interfaces.flockIPasswordOrigin))
  551.         { 
  552.           throw Components.interfaces.NS_ERROR_NO_INTERFACE;
  553.         }
  554.         return this;
  555.       },
  556.       user: results.getPropertyAsAString("username"),
  557.       password: results.getPropertyAsAString("password"),
  558.       host: null,
  559.       formType: formType
  560.     };
  561.  
  562.     // Doing a bit of a hack here...
  563.     // Since the Piczo login landing page doesn't always reveal which
  564.     // account is logged in, we will need to look at the session cookie and
  565.     // see if its the same as what it was when the user last logged in.  So
  566.     // at this point we're just storing the account username as a temp
  567.     // password entry associated with the session cookie token.
  568.     var sessionValue = this.acUtils.getCookie("http://piczo.com",
  569.                                               gStrings["sessioncookie"]);
  570.     if (!sessionValue) {
  571.       sessionValue = this.acUtils.getCookie("http://www.piczo.com",
  572.                                             gStrings["sessioncookie"]);
  573.     }
  574.     this.acUtils.clearTempPassword("piczo:session:" + sessionValue);
  575.     this.acUtils.setTempPassword("piczo:session:" + sessionValue, pw.user, "",
  576.                                  formType);
  577.     return pw;
  578.   }
  579.   return null;
  580. }
  581.  
  582. piczoService.prototype._getAccountIDFromGUID =
  583. function piczoService__getAccountIDFromGUID(aGUID)
  584. {
  585.   _logger.info("_getAccountIDFromGUID('"+aGUID+"')");
  586.   var accts = this._coop.Account.find({serviceId: CONTRACT_ID, piczo_guid: aGUID});
  587.   return (accts.length) ? accts[0].accountId : null;
  588. }
  589.  
  590. piczoService.prototype.updateAccountStatusFromDocument =
  591. function piczoService_updateAccountStatusFromDocument(aDocument)
  592. {
  593.   _logger.info("{flockIManageableWebService}.updateAccountStatusFromDocument()");
  594.   if (this.webDetective.detect("piczo", "loggedout", aDocument, null)) {
  595.     this.acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
  596.   } else if (this.webDetective.detect("piczo", "loggedin", aDocument, null)) {
  597.     var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  598.                             .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  599.     var accountID;
  600.     var profileURL;
  601.     if (this.webDetective.detect("piczo", "accountinfo", aDocument, results)) {
  602.       accountID = this._getAccountIDFromGUID(results.getPropertyAsAString("piczo_guid"));
  603.       profileURL = results.getPropertyAsAString("profileURL");
  604.     } else {
  605.       // Since some login landing pages don't have account info on them that we
  606.       // can detect, we can try seeing if there's a session cookie stored as a
  607.       // temp password entry.  (This is a hack.)
  608.       var sessionValue = this.acUtils.getCookie("http://piczo.com",
  609.                                                 gStrings["sessioncookie"]);
  610.       if (!sessionValue) {
  611.         sessionValue = this.acUtils.getCookie("http://www.piczo.com",
  612.                                               gStrings["sessioncookie"]);
  613.       }
  614.       var pw = this.acUtils.getTempPassword("piczo:session:"+sessionValue);
  615.       if (pw) {
  616.         _logger.info("Sneaky! Got acctID from a temp password associated with session cookie!");
  617.         accountID = pw.user;
  618.       }
  619.     }
  620.     if (accountID && accountID.length) {
  621.       var accountURN = this.acUtils.getAccountURNById(this.urn, accountID);
  622.       if (accountURN) {
  623.         var c_acct = this._coop.get(accountURN);
  624.         var results2 = getCompTK().newResults();
  625.         if (this.webDetective.detect("piczo", "avatar", aDocument, results2)) {
  626.           c_acct.avatar = results2.getPropertyAsAString("avatarURL");
  627.         }
  628.         if (!c_acct.isAuthenticated) {
  629.           var acct = this.getAccount(accountURN);
  630.           acct.activate(null);
  631.           // Assume the API login will succeed.  The listener should handle any
  632.           // failures...
  633.           c_acct.isAuthenticated = true;
  634.         }
  635.         if (profileURL && profileURL.length) {
  636.           c_acct.URL = profileURL;
  637.         }
  638.       }
  639.     }
  640.   }
  641. }
  642. // END flockIManageableWebService interface
  643.  
  644. ///////////////////////////////////////
  645. // BEGIN flockIPollingService interface
  646. ///////////////////////////////////////
  647. piczoService.prototype.refresh =
  648. function piczoService_refresh(aURN, aPollingListener)
  649. {
  650.   _logger.info("{flockIPollingService}.refresh('"+aURN+"')");
  651.   if (this.mAPI && this.mAPI.isLoggedIn()) {
  652.     var svc = this;
  653.     var friendsListener = {
  654.       onSuccess: function (aSubject, aTopic) {
  655.         _logger.info(".refresh() friendsListener.onSuccess()");
  656.         var friends = aSubject;
  657.         var acctURN = svc.acUtils.getFirstAuthenticatedAccountForService(CONTRACT_ID);
  658.         var c_acct = svc._coop.get(acctURN);
  659.         for (var i = 0; i < friends.length; i++) {
  660.           _logger.info(" found person: id=["+friends[i].id+"] name=["+friends[i].name+"] avatarURL=["+friends[i].avatarURL+"]");
  661.           // XXX TODO FIXME: implement
  662.           var identURN = "urn:flock:identity:piczo:"+c_acct.accountId+":"+friends[i].id;
  663.           var notieURN = "urn:flock:notifications:piczo:"+c_acct.accoutId+":"+friends[i].id;
  664.           var c_ident = svc._coop.get(identURN);
  665.           var bUpdating = (c_ident) ? true : false;
  666.           if (!bUpdating) {
  667.             c_ident = new svc._coop.Identity(
  668.               identURN,
  669.               {
  670.                 accountId: c_acct.accountId,
  671.                 name: friends[i].name,
  672.                 favicon: svc.icon,
  673.                 isPollable: false,
  674.                 serviceId: CONTRACT_ID,
  675.                 avatar: friends[i].avatarURL,
  676.                 datevalue: new Date()
  677.               }
  678.             );
  679.             c_ident.account.addOnce(c_acct);
  680.  
  681.             var c_nStream = new svc._coop.Stream(
  682.               notieURN,
  683.               {
  684.                 name: "Piczo notifications for "+friends[i].name,
  685.                 isPollable: false,
  686.                 isIndexable: false,
  687.                 notify: true,
  688.                 serviceId: CONTRACT_ID
  689.               }
  690.             );
  691.             c_ident.children.addOnce(c_nStream);
  692.  
  693.             var c_person = new svc._coop.Person({
  694.               name: friends[i].name,
  695.               avatar: friends[i].avatarURL,
  696.               favicon: svc.icon
  697.             });
  698.             c_person.children.add(c_ident);
  699.             svc.person_root.children.add(c_person);
  700.           }
  701.           c_ident.name = friends[i].name;
  702.           c_ident.avatar = friends[i].avatarURL;
  703.           c_ident.getParent().avatar = friends[i].avatarURL;
  704.           c_ident.URL = friends[i].URL;
  705.           c_ident.getParent().URL = friends[i].URL;
  706.           svc.updateActions(identURN);
  707.           aPollingListener.onResult();
  708.         }
  709.       },
  710.       onError: function (aSubject, aTopic, aError) {
  711.         _logger.info(".refresh() friendsListener.onError()");
  712.       }
  713.     };
  714.     //this.mAPI.getFriends(friendsListener);
  715.     aPollingListener.onResult();
  716.   }
  717. }
  718. // END flockIPollingService interface
  719.  
  720.  
  721. // BEGIN flockIPeopleActionController
  722. piczoService.prototype.doAction =
  723. function piczoService_doAction(aActionName, aURN, aSubject)
  724. {
  725.   _logger.info("{flockIPeopleActionController}.doAction('"+aActionName+"', '"+aURN+"')");
  726.   var c_obj = this._coop.get(aURN);
  727.   switch (aActionName) {
  728.     case "openProfile":
  729.     {  
  730.       var accountId;
  731.       if (c_obj.isInstanceOf(this._coop.Identity)) {
  732.         return c_obj.URL;
  733.       } else if (c_obj.isInstanceOf(this._coop.Account)) {
  734.         accountId = c_obj.accountId;
  735.       } 
  736.       return gStrings["userprofile"].replace("%accountid%", accountId);
  737.     }; break;
  738.     case "editProfile":
  739.       break;
  740.   }
  741. }
  742.  
  743. piczoService.prototype.updateActions =
  744. function piczoService_updateActions(aURN)
  745. {
  746.   var coopObj = this._coop.get(aURN);
  747.   if (coopObj.isInstanceOf(this._coop.Account)) {
  748.     coopObj.enabledAction.removeAll();
  749.     var serviceActions = this.c_svc.children.enumerate();
  750.     while (serviceActions.hasMoreElements()) {
  751.       var action = serviceActions.getNext();
  752.       if (action.flavour == "accountaction" ||
  753.           action.flavour == "view")
  754.       {
  755.         coopObj.enabledAction.add(action);
  756.       }
  757.     }
  758.   }
  759.   
  760. }
  761. // END flockIPeopleActionController
  762.  
  763.  
  764. piczoService.prototype.init =
  765. function piczoService_init()
  766. {
  767.   var _logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  768.   _logger.init("piczoservice");
  769.  
  770.   _logger.info(".init()");
  771.  
  772.   // Prevent re-entry
  773.   if (this.mIsInitialized) return;
  774.   this.prefService = Components.classes["@mozilla.org/preferences-service;1"]
  775.                                .getService(Components.interfaces.nsIPrefBranch);
  776.   if (this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
  777.       !this.prefService.getBoolPref(SERVICE_ENABLED_PREF))
  778.   {
  779.     _logger.info("Pref "+SERVICE_ENABLED_PREF+" set to FALSE... not initializing.");
  780.     var catMgr = Cc["@mozilla.org/categorymanager;1"]
  781.       .getService(Ci.nsICategoryManager);
  782.     catMgr.deleteCategoryEntry( "wsm-startup", CATEGORY_COMPONENT_NAME, true ); 
  783.     catMgr.deleteCategoryEntry( "flockIPhotoAPI", CATEGORY_ENTRY_NAME, true );
  784.     catMgr.deleteCategoryEntry( "flockWebService", CATEGORY_ENTRY_NAME, true ); 
  785.     return;
  786.   }
  787.   this.mIsInitialized = true;
  788.  
  789.   // Initialize API
  790.   this.mAPI = new piczoAPI();
  791.   gAPI = this.mAPI;
  792.   gAPI.defAlbum = {};
  793.  
  794.   // Init global service handle
  795.   gSVC = this;
  796.  
  797.   // Initialize Coop, service and actions
  798.   this._coop = Components.classes["@flock.com/singleton;1"]
  799.                          .getService(Components.interfaces.flockISingleton)
  800.                          .getSingleton("chrome://browser/content/flock/common/load-faves-coop.js")
  801.                          .wrappedJSObject;
  802.   this.person_root = new this._coop.Folder("urn:piczo:peopleroot", {name: "Piczo People"});
  803.  
  804.   this.urn = "urn:piczo:service";
  805.   this.c_svc = new this._coop.Service(
  806.     this.urn, {
  807.       name: CLASS_NAME,
  808.       desc: CLASS_DESC,
  809.       serviceId: CONTRACT_ID,
  810.       contactLabel: "Friends"
  811.     }
  812.   );
  813.  
  814.   this.c_svc.children.addOnce(
  815.     new this._coop.Action(
  816.       "urn:piczo:actions:openprofile", {
  817.         name: "Open Profile",
  818.         method: "openProfile",
  819.         service: CONTRACT_ID,
  820.         flavour: "view"
  821.       }
  822.     )
  823.   );
  824.   this.c_svc.children.addOnce(
  825.     new this._coop.Action(
  826.       "urn:piczo:actions:editprofile", {
  827.         name: "Edit Profile",
  828.         method: "editProfile",
  829.         service: CONTRACT_ID,
  830.         flavour: "accountaction"
  831.       }
  832.     )
  833.   );
  834.  
  835.   // Load Web Detective and update strings
  836.   this.webDetective = this.acUtils.useWebDetective("piczo.xml");
  837.   for (var s in gStrings) {
  838.     gStrings[s] = this.webDetective.getString("piczo", s, gStrings[s]);
  839.   }
  840.   this.url            = gStrings["mainpage"];
  841.   this.domains        = gStrings["domains"];
  842.   this.c_svc.domains  = gStrings["domains"];
  843.   this.icon           = gStrings["favicon"];
  844.   this.c_svc.loginURL = gStrings["userlogin"];
  845.  
  846.   // Update account auth states
  847.   if (this.webDetective.detectCookies("piczo", "loggedout", null)) {
  848.     this.acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
  849.   } else {
  850.     var acctURN = this.acUtils.getFirstAuthenticatedAccountForService(CONTRACT_ID);
  851.     try {
  852.       if (acctURN) {
  853.         var acct = this.getAccount(acctURN);
  854.         acct.activate(null);
  855.       }
  856.     } catch(e) {
  857.       _logger.info(".init() ERROR: " + e);
  858.     }
  859.   }
  860.  
  861.   // Update account actions
  862. /*  var accts = this.getAccounts();
  863.   while (accts.hasMoreElements()) {
  864.     this.updateActions(
  865.       accts.getNext()
  866.            .QueryInterface(Components.interfaces.flockIWebServiceAccount)
  867.            .urn
  868.     );
  869.   }
  870.   */
  871. }
  872.  
  873. // ========== END piczoService class ==========
  874.  
  875. // ============================================================================================================================
  876. // ========== BEGIN piczoAPI class ============================================================================================
  877. // ============================================================================================================================
  878.  
  879. function piczoAPI()
  880. {
  881.   _logger.info("[piczoAPI] CONSTRUCTOR");
  882.   this.mAPIToken = null;
  883.   this.mAPITokenID = null;
  884.   this.mSession = {
  885.     token: null,
  886.     userGUID: null,
  887.     username: null,
  888.     password: null
  889.   };
  890.   this.mTS = 0;
  891.   this.cryptoHash = Components.classes["@mozilla.org/security/hash;1"]
  892.                               .createInstance(Components.interfaces.nsICryptoHash);
  893. }
  894.  
  895. piczoAPI.prototype.getAuthUser =
  896. function() {
  897.   return this.mSession; 
  898. }
  899.  
  900. piczoAPI.prototype.QueryInterface = function(iid) {
  901.   if (!iid.equals(Ci.nsIClassInfo) &&
  902.       !iid.equals(Ci.nsISupports)) {
  903.     throw Cr.NS_ERROR_NO_INTERFACE;
  904.   }
  905.   return this;
  906. }
  907.  
  908. piczoAPI.prototype.getTS =
  909. function piczoAPI_getTS()
  910. {
  911.   return (this.mTS++);
  912. }
  913.  
  914. piczoAPI.prototype.call =
  915. function piczoAPI_call(aMethod, aURL, aCustomHeaders, aPostVars, aListener, aSign)
  916. {
  917.   aMethod = aMethod.toUpperCase();
  918.   _logger.info("[piczoAPI].call('"+aMethod+"', '"+aURL+"')");
  919.   var hr = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1']
  920.                      .createInstance(Components.interfaces.nsIXMLHttpRequest);
  921.   hr.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
  922.   hr.open(aMethod, aURL, true);
  923.   if (aMethod == "POST") {
  924.     hr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded", false);
  925.   }
  926.   // Per Bug #9373, Piczo API chokes on '+' characters in post vars, so we
  927.   // need this function to explicitly encode them.
  928.   var customEscape = function piczoAPI_customEscape(aText) {
  929.     return escape(aText).replace("+", "%2B");
  930.   };
  931.   if (aSign) {
  932.     // Need to compose message string and generate signature
  933.     var url = aURL;
  934.     var params = [];
  935.     var qmIdx = url.indexOf("?");
  936.     if (qmIdx > -1) {
  937.       var qString = url.substring(qmIdx + 1);
  938.       url = url.substring(0, qmIdx);
  939.       var qArr = qString.split("&");
  940.       for (var i = 0; i < qArr.length; i++) {
  941.         var prm = qArr[i].split("=");
  942.         params.push([prm[0], prm[1]]);
  943.       }
  944.     }
  945.     if (aCustomHeaders) {
  946.       for (var h in aCustomHeaders) {
  947.         params.push([h, aCustomHeaders[h]]);
  948.       }
  949.     }
  950.     if (aPostVars) {
  951.       for (var p in aPostVars) {
  952.         params.push([p, aPostVars[p]]);
  953.       }
  954.     }
  955.     params.sort();
  956.  
  957.     ////////////////////////////////////////////////////////
  958.     // Piczo Digital Signature format:
  959.     // ds := base-64( md5( {api-token} + {access-token} ) )
  960.     ////////////////////////////////////////////////////////
  961.     var apitoken = "";
  962.     var accesstoken = "";
  963.     for (var i = 0; i < params.length; i++) {
  964.       if(params[i][0] == "at") {
  965.         accesstoken = params[i][1];
  966.       } else if(params[i][0] == "api-token") {
  967.         apitoken = params[i][1];
  968.       }
  969.     }
  970.     var msg = apitoken + accesstoken;
  971.     var msgArr = [];
  972.     for (var i = 0; i < msg.length; i++) {
  973.       msgArr.push(msg.charCodeAt(i));
  974.     }
  975.     this.cryptoHash.init(Components.interfaces.nsICryptoHash.MD5);
  976.     this.cryptoHash.update(msgArr, msgArr.length);
  977.     var sig = this.cryptoHash.finish(true);
  978.     var dsValue = escape(sig);
  979.     dsValue = dsValue.replace(/\+/g,"%2B");
  980.     dsValue = dsValue.replace(/\//g,"%2F");
  981.     dsValue = dsValue.replace(/\@/g,"%40");
  982.     _logger.info("message: "+msg+"  sig: "+dsValue);
  983.     hr.setRequestHeader("ds", dsValue);
  984.   }
  985.   if (aCustomHeaders) {
  986.     for (var h in aCustomHeaders) {
  987.       var val = escape(aCustomHeaders[h]);
  988.       val = val.replace(/\+/g,"%2B");
  989.       val = val.replace(/\//g,"%2F");
  990.       val = val.replace(/\@/g,"%40");
  991.       hr.setRequestHeader(h, val);
  992.     }
  993.   }
  994.   hr.onreadystatechange = function (aEvent) {
  995.     _logger.info("[piczoAPI].call(): hr.onreadystatechange(): "+hr.readyState);
  996.     if (aListener) {
  997.       if (hr.readyState == 4) {
  998.         try {
  999.           if (hr.status/100 == 2) {
  1000.             try {
  1001.               aListener.onSuccess(hr, aURL);
  1002.             } catch (ex) {
  1003.               _logger.info(ex);
  1004.               aListener.onError(hr, aURL, null);
  1005.             }
  1006.           } else {
  1007.             // HTTP error
  1008.             var err = {
  1009.               errorCode: hr.status,
  1010.               errorString: "HTTP error."
  1011.             };
  1012.             aListener.onError(hr, aURL, err);
  1013.           }
  1014.         } catch (ex) {
  1015.           _logger.info(ex);
  1016.           var err = {
  1017.             errorCode: Components.interfaces.flockIError.PHOTOSERVICE_UNAVAILABLE,
  1018.             errorString: "Request did not complete."
  1019.           };
  1020.           aListener.onError(hr, aURL, err);
  1021.         }
  1022.       }
  1023.     }
  1024.   };
  1025.   var postBody = "";
  1026.   if (aPostVars) {
  1027.     for (var v in aPostVars) {
  1028.       if (postBody.length) { postBody += "&"; }
  1029.       postBody += v+"="+customEscape(aPostVars[v]);
  1030.     }
  1031.   }
  1032.   if ((aMethod == "POST") && postBody && postBody.length) {
  1033.     hr.send(postBody);
  1034.   } else {
  1035.     hr.send(null);
  1036.   }
  1037. }
  1038.  
  1039. piczoAPI.prototype.getAPIToken =
  1040. function piczoAPI_getAPIToken(aListener)
  1041. {
  1042.   _logger.info("[piczoAPI].getAPIToken()");
  1043.   var api = this;
  1044.   var setupListener = {
  1045.     onSuccess: function (aSubject, aTopic) {
  1046.       _logger.info("[piczoAPI].getAPIToken() setupListener.onSuccess()");
  1047.       _logger.info(aSubject.responseText);
  1048.       var xmlDoc = aSubject.responseXML;
  1049.       var replyEl = xmlDoc.getElementsByTagName("setup.reply").item(0);
  1050.       var tokenEl = replyEl.getElementsByTagName("api-token").item(0);
  1051.       var tokenIDEl = replyEl.getElementsByTagName("id").item(0);
  1052.       api.mAPIToken = tokenEl.childNodes.item(0).nodeValue;
  1053.       api.mAPITokenID = tokenIDEl.childNodes.item(0).nodeValue;
  1054.       _logger.info(" got API token ID:" + api.mAPITokenID);
  1055.       _logger.info(" got API token: "+api.mAPIToken);
  1056.       if (aListener) { aListener.onSuccess(aSubject, aTopic); }
  1057.     },
  1058.     onError: function (aSubject, aTopic, aError) {
  1059.       _logger.info("[piczoAPI].getAPIToken() setupListener.onError()");
  1060.       if (aError) {
  1061.         for (var p in aError) {
  1062.           _logger.info("error["+p+"] = "+aError[p]);
  1063.         }
  1064.       }
  1065.       for (var p in aSubject) {
  1066.         _logger.info("response["+p+"] = "+aSubject[p]);
  1067.       }
  1068.       if (aListener) { aListener.onError(aSubject, aTopic, aError); }
  1069.     }
  1070.   };
  1071.   var url = gStrings["api-setup-URL"].replace("%agent%", gStrings["api-agent-string"]);
  1072.   var postVars = {
  1073.     "api-agent": gStrings["api-agent-string"]
  1074.   };
  1075.   this.call("post", url, null, postVars, setupListener);
  1076. }
  1077.  
  1078. piczoAPI.prototype.getPhotosCall =
  1079. function piczoService_authenticatedGetPhotosCall(aListener, aMethod, aDictionary)
  1080. {
  1081.   var api = this;
  1082.   api.pageID = null;
  1083.   api.pageToFind = aDictionary.albumlabel;
  1084.  
  1085.   _logger.info("piczoService_authenticatedCall " + aMethod + "\n");
  1086.   if (!this.isLoggedIn) {
  1087.     _logger.info('not logged in, so trying unauth call for ' + aMethod);
  1088.     return this.call(aListener, aMethod, aDictionary);
  1089.   }
  1090.   
  1091.   var pageToFind = aDictionary.albumlabel;
  1092.  
  1093.   var callGetPhotos = function () {
  1094.     var url = "http://api.piczo.com/api-1/lookup";
  1095.  
  1096.     if (api.pageID) {
  1097.       url += "/" + api.pageID; 
  1098.     }
  1099.  
  1100.     url += "/images?reply=rss";
  1101.  
  1102.     var headers = {
  1103.       "api-token": this.gAPI.mAPIToken,
  1104.       "at": this.gAPI.mSession.token
  1105.     };
  1106.     api.call("get", url, headers, null, aListener, true);
  1107.   };
  1108.  
  1109.   var pagesListener = {
  1110.     onSuccess: function pagesListener_onSuccess(aSubject, aTopic) {
  1111.       var xml = aSubject.responseXML;
  1112.       _logger.info("[piczoAPI].getPages returned: "
  1113.                    + aSubject.responseText + "\n");
  1114.  
  1115.       var items = xml.getElementsByTagName("item");
  1116.  
  1117.       for (var i = 0; i < items.length; i++) {
  1118.         var guid = items[i].getElementsByTagName("guid")[0]
  1119.                    .firstChild.nodeValue;
  1120.         var pagelabel = items[i].getElementsByTagName("description")[0]
  1121.                         .firstChild.nodeValue;
  1122.         
  1123.         if (api.pageToFind == pagelabel) {
  1124.           api.pageID = guid;
  1125.         }
  1126.       }
  1127.  
  1128.       callGetPhotos();
  1129.       return;
  1130.     },
  1131.     onError: function pagesListener_onError(aSubject, aTopic, aFlockError) {
  1132.       _logger.error("Error getting pages" + aFlockError.errorString);
  1133.       if (api.handleStaleToken(aSubject)) {
  1134.         // Resubmit the request with the new token
  1135.         api.getPhotosCall(aListener, 'piczo.photos.get', aDictionary);
  1136.       } else {
  1137.         aListener.onError(aFlockError);
  1138.       }
  1139.     } 
  1140.   }
  1141.  
  1142.   // Get Page ID first
  1143.   var callGetPages = function callGetPages_function() {
  1144.     var url = "http://api.piczo.com/api-1/lookup" + "/pages?reply=rss";
  1145.  
  1146.     var headers = {
  1147.       "api-token": this.gAPI.mAPIToken,
  1148.       "at": this.gAPI.mSession.token
  1149.     };
  1150.     api.call("get", url, headers, null, pagesListener, true);
  1151.   }
  1152.  
  1153.   if (this.mAPIToken) {
  1154.     _logger.info("piczoService Calling getphotos\n");
  1155.     if (pageToFind != null) {
  1156.       callGetPages();
  1157.     } else {
  1158.       callGetPhotos();
  1159.     }
  1160.   } else {
  1161.     _logger.info("piczoService wasnt logged in....\n");
  1162.     // We are not logged in, force user to log in now.
  1163.     var aError = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  1164.     aError.errorCode   = Ci.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
  1165.     aListener.onError(aError);
  1166.   } 
  1167.   //return this._doCall(aListener, aMethod, aDictionary, true);
  1168. }
  1169.  
  1170. piczoAPI.prototype.getAccountStatus = 
  1171. function piczoAPI_getAccountStatus(aListener) {
  1172.   _logger.info("[piczoAPI].getAccountStatus\n");
  1173. }
  1174.  
  1175. piczoAPI.prototype.getAlbums = 
  1176. function piczoAPI_getAlbums(aListener, aParams) {
  1177.   _logger.info("[piczoAPI].getAlbums('" + aParams + "')\n");
  1178.  
  1179.   var api = this;
  1180.  
  1181.   // Make inbox call
  1182.   //
  1183.   var listener = {
  1184.     onSuccess: function(aSubject, aTopic) {
  1185.       //try {
  1186.         var xml          = aSubject.responseXML;
  1187.         _logger.info("[piczoAPI].getInbox returned: " + aSubject.responseText+ "\n");
  1188.         var destinations = xml.getElementsByTagName("destination");
  1189.  
  1190.         var photoAlbums = [];
  1191.         _logger.info("[piczoAPI].getInbox parsing data: " + destinations.length + "\n");
  1192.  
  1193.         gAPI.defAlbum = {};
  1194.  
  1195.         for (var i = 0; i < destinations.length; i++) {
  1196.           var name = destinations[i].getElementsByTagName('name')[0].firstChild.nodeValue;
  1197.           var uri = destinations[i].getElementsByTagName('action-uri')[0].firstChild.nodeValue;
  1198.  
  1199.           _logger.info("[piczoAPI].getInbox data: " + name + ", " + uri + "\n");
  1200.  
  1201.           var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID]
  1202.                                    .createInstance(Ci.flockIPhotoAlbum);
  1203.           newAlbum.id = uri;
  1204.           newAlbum.title = name;
  1205.  
  1206.           if(!gAPI.defAlbum.id) {
  1207.             gAPI.defAlbum = newAlbum;
  1208.           }
  1209.  
  1210.           photoAlbums.push(newAlbum);
  1211.         }
  1212.  
  1213.         var enum_ = {
  1214.           hasMoreElements: function() {
  1215.             return (photoAlbums.length>0);
  1216.           },
  1217.           getNext: function() {
  1218.             return photoAlbums.shift();
  1219.           }
  1220.         }
  1221.  
  1222.         _logger.info("[piczoAPI].getInbox returning results...\n");
  1223.  
  1224.         aListener.onResult(enum_, null);
  1225.       //} catch(e) {
  1226.       //  _logger.info("[piczoAPI].getInbox ERROR: " + aSubject.responseText + "\n");
  1227.       //  aListener.onError(null); 
  1228.      // }
  1229.     },
  1230.     onError: function(aSubject, aTopic, aFlockError) {
  1231.       _logger.info('Error getting inbox' + aFlockError.errorString);
  1232.       if (api.handleStaleToken(aSubject)) {
  1233.         // Resubmit the request with the new token
  1234.         api.getAlbums(aListener,aParams);
  1235.       } else {
  1236.         aListener.onError(aFlockError);
  1237.       }
  1238.     }
  1239.   }
  1240.  
  1241.   var callGetInbox = function () {
  1242.     var url = "http://api.piczo.com/api-1/inbox" + "/images";
  1243.  
  1244.     var headers = {
  1245.       "api-token": this.gAPI.mAPIToken,
  1246.       "at": this.gAPI.mSession.token
  1247.     };
  1248.     api.call("get", url, headers, null, listener, true);
  1249.   };
  1250.  
  1251.  
  1252.   if (this.mAPIToken) {
  1253.     _logger.info("piczoService Calling getInbox\n");
  1254.     callGetInbox();
  1255.   } else {
  1256.     _logger.info("piczoService wasnt logged in....\n");
  1257.     // We are not logged in, force user to log in now.
  1258.     var aError = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  1259.     aError.errorCode   = Ci.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
  1260.     aListener.onError(aError);
  1261.     return;
  1262.   }
  1263. }
  1264.  
  1265.  
  1266. // Handle the case where the session token goes stale
  1267. // If stale reset and return true, otherwise return false
  1268. piczoAPI.prototype.handleStaleToken =
  1269. function(aSubject) {
  1270.   
  1271.   // When a session token goes stale the piczo api  
  1272.   // marks the response with an http error (401 - unauthorized)
  1273.   // and includes error information with a new session token 
  1274.   
  1275.   _logger.info("handleStaleToken()");
  1276.   var api = this;
  1277.   if (aSubject.status == 401) {
  1278.     var xml = aSubject.responseXML;
  1279.     _logger.info(aSubject.responseText);
  1280.     
  1281.     var errorEl = xml.getElementsByTagName("errors").item(0);
  1282.     if (!errorEl || (errorEl.length == 0)) {
  1283.       return false;
  1284.     }
  1285.  
  1286.     var invalidCrdntlEl = errorEl.getElementsByTagName("error.invalid-credential").item(0);
  1287.     if (!invalidCrdntlEl || (invalidCrdntlEl.length == 0)) {
  1288.       return false;
  1289.     }
  1290.  
  1291.     var arglinkEl = invalidCrdntlEl.getElementsByTagName("argument").item(0);
  1292.     if (!arglinkEl || (arglinkEl.length == 0)) {
  1293.       return false;
  1294.     }
  1295.  
  1296.     var problem = arglinkEl.getAttribute("problem");
  1297.     if (!problem) {
  1298.       return false;
  1299.     }
  1300.  
  1301.     _logger.info("problem = " + problem );
  1302.     if (problem == "expired-access") { 
  1303.       // Our access token went stale so grab the new one from the response
  1304.       var sessiontokenEl = errorEl.getElementsByTagName("at").item(0);
  1305.       var sessiontoken = sessiontokenEl.childNodes.item(0).nodeValue;
  1306.       _logger.info("new access token = " + sessiontoken);
  1307.       api.mSession.token = sessiontoken;
  1308.       return true;
  1309.     }
  1310.   }
  1311.   return false; 
  1312. }
  1313.  
  1314. piczoAPI.prototype.getPhotos = 
  1315. function piczoAPI_getPhotos(aListener, aParams) {
  1316.   _logger.info("[piczoAPI].getPhotos('" + aParams + "')\n");
  1317.   var api = this;
  1318.   var listener = {
  1319.     onSuccess: function(aSubject, aTopic) {
  1320.       var photos = [];
  1321.       _logger.info("[piczoAPI].getPhotos returned: " + aSubject.responseText + "\n");
  1322.       var xml = aSubject.responseXML;
  1323.  
  1324.       var photoList = xml.getElementsByTagName("item");
  1325.       for (var i = 0; i < photoList.length; i++) {
  1326.         var photo = photoList[i];
  1327.  
  1328.         var id = photo.getElementsByTagName('guid')[0].firstChild.nodeValue;
  1329.         var link = photo.getElementsByTagName('link')[0].firstChild.nodeValue;
  1330.         var description = photo.getElementsByTagName('description')[0].firstChild.nodeValue;
  1331.         var thumbnail = photo.getElementsByTagName("thumbnail")
  1332.                              .item(0)
  1333.                              .getAttribute("url");
  1334.  
  1335.         var newPhoto = new piczoPhoto();
  1336.         newPhoto.webPageUrl = link;
  1337.         newPhoto.thumbnail = link;
  1338.         newPhoto.midSizePhoto = thumbnail;
  1339.         newPhoto.largeSizePhoto = link;
  1340.         newPhoto.title = description;
  1341.         newPhoto.id = id;
  1342.         newPhoto.is_public = true;
  1343.         newPhoto.is_video = false;
  1344.  
  1345.         photos.unshift(newPhoto);
  1346.       }
  1347.  
  1348.       aListener.onSuccess(createEnum(photos), 'success');
  1349.     },
  1350.     onError: function(aSubject, aTopic, aFlockError) {
  1351.       if(aFlockError) {
  1352.         _logger.info('Error getting photos' + aFlockError.errorString);
  1353.       } else {
  1354.         _logger.info("Error getting photos");
  1355.       }
  1356.  
  1357.       if (api.handleStaleToken(aSubject)) {
  1358.         // Resubmit the request with the new token
  1359.         api.getPhotos(aListener,aParams);
  1360.       } else {
  1361.         aListener.onError(aFlockError);
  1362.       }
  1363.     }
  1364.   }
  1365.  
  1366.   var params = {
  1367.     subj_id: aParams.getPropertyAsAString("subj_id"),
  1368.     albumlabel: aParams.getPropertyAsAString("albumlabel")
  1369.   };
  1370.  
  1371.   _logger.info("calling getphotoscall"); 
  1372.   api.getPhotosCall(listener, 'piczo.photos.get', params);
  1373. }
  1374.  
  1375. piczoAPI.prototype.login =
  1376. function piczoAPI_login(aUsername, aPassword, aListener)
  1377. {
  1378.   _logger.info("[piczoAPI].login('"+aUsername+"')");
  1379.   var api = this;
  1380.   if (aUsername) { this.mSession.username = aUsername; }
  1381.   if (aPassword) { this.mSession.password = aPassword; }
  1382.  
  1383.   var loginListener = {
  1384.     onSuccess: function (aSubject, aTopic) {
  1385.       _logger.info("[piczoAPI].login() loginListener.onSuccess()");
  1386.       _logger.info(aSubject.responseText);
  1387.       var xmlDoc = aSubject.responseXML;
  1388.       var replyEl = xmlDoc.getElementsByTagName("login.reply").item(0);
  1389.       var userlinkEl = replyEl.getElementsByTagName("user.id").item(0);
  1390.       api.mSession.userGUID = userlinkEl.getAttribute("guid");
  1391.       _logger.info(" got user GUID: "+api.mSession.userGUID);
  1392.       var sessiontokenEl = replyEl.getElementsByTagName("at").item(0);
  1393.       api.mSession.token = sessiontokenEl.childNodes.item(0).nodeValue;
  1394.       _logger.info(" got session token: "+api.mSession.token);
  1395.   
  1396.       if (aListener) { aListener.onSuccess(aSubject, aTopic); }
  1397.     },
  1398.     onError: function (aSubject, aTopic, aError) {
  1399.       _logger.info("[piczoAPI].login() loginListener.onError()");
  1400.       if (aListener) { aListener.onError(aSubject, aTopic, aError); }
  1401.     }
  1402.   };
  1403.  
  1404.   var callLogin = function () {
  1405.     var url = gStrings["api-login-URL"];
  1406.     var headers = {
  1407.       "ts": api.getTS()
  1408.     };
  1409.     var postVars = {
  1410.       "user-name": api.mSession.username,
  1411.       "user-password": api.mSession.password,
  1412.       "id": api.mAPITokenID,
  1413.       "api-token": api.mAPIToken
  1414.     };
  1415.     api.call("post", url, headers, postVars, loginListener);
  1416.   };
  1417.  
  1418.   if (this.mAPIToken) {
  1419.     callLogin();
  1420.   } else {
  1421.     // Need to get an API token before we can log in
  1422.     var tokenListener = {
  1423.       onSuccess: function (aSubject, aTopic) {
  1424.         callLogin();
  1425.       },
  1426.       onError: function (aSubject, aTopic, aError) {
  1427.         if (aListener) { aListener.onError(aSubject, aTopic, aError); }
  1428.       }
  1429.     };
  1430.     this.getAPIToken(tokenListener);
  1431.   }
  1432. }
  1433.  
  1434. piczoAPI.prototype.logout =
  1435. function piczoAPI_logout()
  1436. {
  1437.   _logger.info("[piczoAPI].logout()");
  1438.   this.mSession = [];
  1439.   
  1440.   var api = this;
  1441.  
  1442.   var logoutListener = {
  1443.     onSuccess: function (aSubject, aTopic) {
  1444.       _logger.info("[piczoAPI].logout() logoutListener.onSuccess()");
  1445.       _logger.info(aSubject.responseText);
  1446.       var xmlDoc = aSubject.responseXML;
  1447.       var replyEl = xmlDoc.getElementsByTagName("logout.reply").item(0);
  1448.     },
  1449.     onError: function (aSubject, aTopic, aError) {
  1450.       _logger.info("[piczoAPI].logout() logoutListener.onError()");
  1451.     }
  1452.   };
  1453.  
  1454.   var callLogout = function () {
  1455.     var url = gStrings["api-logout-URL"];
  1456.     var headers = {
  1457.       "ts": api.getTS()
  1458.     };
  1459.     var postVars = {
  1460.       "user-name": api.mSession.username,
  1461.       "user-password": api.mSession.password,
  1462.       "id": api.mAPITokenID,
  1463.       "api-token": api.mAPIToken
  1464.     };
  1465.     api.call("post", url, headers, postVars, logoutListener);
  1466.   };
  1467.  
  1468.   if (this.mAPIToken) {
  1469.     callLogout();
  1470.   } else {
  1471.     // Need to get an API token before we can log out
  1472.     var tokenListener = {
  1473.       onSuccess: function (aSubject, aTopic) {
  1474.         callLogout();
  1475.       },
  1476.       onError: function (aSubject, aTopic, aError) {
  1477.         if (aListener) { aListener.onError(aSubject, aTopic, aError); }
  1478.       }
  1479.     };
  1480.     this.getAPIToken(tokenListener);
  1481.   }  
  1482. }
  1483.  
  1484. piczoAPI.prototype.upload = 
  1485. function piczoAPI_upload(aListener, aPhoto, aParams, aUpload) {
  1486.   _logger.info("piczoAPI_upload");
  1487.   // XXX: TODO
  1488.   var inst = this;
  1489.   this.uploader = new PhotoUploader();
  1490.   var myListener = {
  1491.     onResult: function(aXML) {
  1492.       _logger.info("piczoAPI_upload: Got a result back from uploader: " + aXML.responseText + "\n");
  1493.       aListener.onUploadComplete(aUpload);
  1494.     },
  1495.     onError: function(aErrorCode) {
  1496.       _logger.info("piczoAPI_upload: " + aErrorCode + "\n");
  1497.       aListener.onError(aErrorCode);
  1498.     },
  1499.     onProgress: function(aCurrentProgress) {
  1500.       _logger.info("piczoAPI_upload: Progress");
  1501.       aListener.onProgress(aCurrentProgress);
  1502.     }
  1503.   }
  1504.   gAPI.convertBools(aParams);
  1505.   gAPI.convertTags(aParams);
  1506.  
  1507.   var dsvalue = this.getDSValue();
  1508.  
  1509.   // Check for default album seleciton
  1510.   if(!aUpload.album) {
  1511.     // auto-select the first album
  1512.     aUpload.album = gAPI.defAlbum.id;
  1513.   }
  1514.  
  1515.   this.uploader.setEndpoint(aUpload.album + "&ds=" + dsvalue);
  1516.   this.uploader.upload(myListener, aPhoto, aParams);
  1517. }
  1518.  
  1519. piczoAPI.prototype.getDSValue = 
  1520. function piczAPI_getDSValue() {
  1521.   _logger.info("piczoAPI_getDSValue");
  1522.   var msg = gAPI.mAPIToken + gAPI.mSession.token;
  1523.   var msgArr = [];
  1524.   for (var i = 0; i < msg.length; i++) {
  1525.     msgArr.push(msg.charCodeAt(i));
  1526.   }
  1527.   this.cryptoHash.init(Components.interfaces.nsICryptoHash.MD5);
  1528.   this.cryptoHash.update(msgArr, msgArr.length);
  1529.   var sig = this.cryptoHash.finish(true);
  1530.   var dsValue = escape(sig);
  1531.   dsValue = dsValue.replace(/\+/g,"%2B");
  1532.   dsValue = dsValue.replace(/\//g,"%2F");
  1533.   dsValue = dsValue.replace(/\@/g,"%40");
  1534.  
  1535.   return dsValue;
  1536. }
  1537.  
  1538. piczoAPI.prototype.convertBools = 
  1539. function piczoAPI_convertBools(aParams) {
  1540.   for (var p in aParams) {
  1541.     if (!p.match(/^is/)) continue;
  1542.     // I hope that this doesn't break anything
  1543.     if (aParams[p]=="true") aParams[p] = "1";
  1544.     if (aParams[p]=="false") aParams[p] = "0";
  1545.   }
  1546. }
  1547.  
  1548. piczoAPI.prototype.convertTags = 
  1549. function piczoAPI_convertTags(aParams) {
  1550.   for (var p in aParams) {
  1551.     if (p != "tags") continue;
  1552.     var tags = aParams[p].split(",");
  1553.     for (var i = 0; i < tags.length;++i) {
  1554.       tags[i] = '"' + tags[i] + '"';
  1555.       tags[i] = tags[i].replace(/\"+/g,'"');
  1556.     }
  1557.     aParams[p] = tags.join(",");
  1558.   }
  1559. }
  1560.  
  1561. piczoAPI.prototype.isLoggedIn =
  1562. function piczoAPI_isLoggedIn()
  1563. {
  1564.   return (this.mSession.token && this.mSession.token.length);
  1565. }
  1566.  
  1567. piczoAPI.prototype.handleDragDrop = 
  1568. function piczoAPI_handleDragDrop(url, locX, locY) {
  1569.   _logger.info("piczoAPI_handleDragDrop: " + url + ", " + locX + ", " + locY);
  1570.   // http://server/go/placeimage
  1571.   // parameters:
  1572.   // imgid  (Image Id)
  1573.   // pageid (Destination page id)
  1574.   // sl     (x position)
  1575.   // st     (y position)
  1576.  
  1577.   // Make sure we are logged in
  1578.   // Hack it up
  1579.   var imageID;
  1580.   var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1581.                           .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1582.   if (gSVC.webDetective.detectNoDOM("piczo", "piczoImageID", null, url, results)) {
  1583.     imageID = results.getPropertyAsAString("imageID");
  1584.   }
  1585.  
  1586.   var currTab;
  1587.   // Get active tab
  1588.   var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  1589.   var win = wm.getMostRecentWindow('navigator:browser');
  1590.   if (win) {
  1591.     var gBrowser = win.gBrowser;
  1592.  
  1593.     for(var i=0;i<gBrowser.mTabs.length;i++) {
  1594.       var browser = gBrowser.mTabs[i].linkedBrowser;
  1595.  
  1596.       if(browser.currentURI.spec == gBrowser.currentURI.spec) {
  1597.         // We found the activce tab
  1598.         currTab = browser;
  1599.       }
  1600.     }
  1601.   }
  1602.  
  1603.   // Get active tab's top-level document
  1604.   _logger.info("piczoAPI: document: " + currTab.contentDocument + "\n"); 
  1605.   var theDoc = currTab.contentDocument;
  1606.  
  1607.   // Make sure we're in a piczo edit page
  1608.   results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1609.                       .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1610.   if (!gSVC.webDetective.detect("piczo", "piczoedit", theDoc, results)) {
  1611.     // not a piczo edit page, return.
  1612.     _logger.info("[piczoAPI] handleDragDrop() - Not a piczo edit page");
  1613.     return false;
  1614.   }
  1615.  
  1616.   const PAGEID_STR = "pageid";
  1617.   var serverLoc;
  1618.   var loc = currTab.currentURI.spec;
  1619.   if(loc.match(/:\/\/([^\/]*)\//)) {
  1620.     serverLoc = RegExp.$1;
  1621.   }
  1622.   
  1623.   _logger.info("piczoAPI: ServerLoc = " + serverLoc + "\n");
  1624.  
  1625.   // Get array of top-level documents' immediate children documents (sub-frames)
  1626.   var frames = theDoc.getElementsByTagName('frame');
  1627.   var iframes = theDoc.getElementsByTagName('iframe');
  1628.  
  1629.   // apply webdetective rule to each document and grab pageid
  1630.   var frameFound = null;
  1631.   var pageID = null;
  1632.   results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1633.                           .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1634.   for (var i = 0; i < frames.length; i++) {
  1635.     if (gSVC.webDetective.detect("piczo", "webedit", frames[i].contentDocument, results)) {
  1636.       pageID = results.getPropertyAsAString(PAGEID_STR);
  1637.       frameFound = frames[i];
  1638.       break;
  1639.     }
  1640.   }
  1641.   for (var i = 0; i < iframes.length && !frameFound; i++) {
  1642.     if (gSVC.webDetective.detect("piczo", "webedit", iframes[i].contentDocument, results)) {
  1643.       pageID = results.getPropertyAsAString(PAGEID_STR);
  1644.       frameFound = iframes[i];
  1645.       break;
  1646.     }
  1647.   }
  1648.   _logger.info("piczoAPI: pageid=" + pageID + "\n");
  1649.  
  1650.   // Setup the url 
  1651.   var aURL = "http://" + serverLoc
  1652.               + "/go/placeimage?imgid=" + imageID
  1653.               + "&pageid=" + pageID
  1654.               + "&sl=" + locX 
  1655.               + "&st=" + locY;
  1656.  
  1657.   // Handler for response.
  1658.   var listener = {
  1659.     onSuccess: function (aSubject, aTopic) {
  1660.       _logger.info("[piczoAPI].handleDragDrop() handleDragDropListener.onSuccess()");
  1661.       _logger.debug("page frame before string mangling: " + frameFound.src + "\n");
  1662.  
  1663.       // need to do a little string mangling to fool the service to reload a
  1664.       // child frame instead of the parent page
  1665.       // pass in the child frame pageid and reload
  1666.       var re = /pageid=\d+/;
  1667.       frameFound.src = frameFound.src.replace(re, PAGEID_STR + "=" + pageID);
  1668.  
  1669.       _logger.debug("page frame after string mangling: " + frameFound.src);
  1670.       return true;
  1671.     },
  1672.     onError: function (flockError) {
  1673.       return false;
  1674.     }
  1675.   }
  1676.  
  1677.   // Make simple xmlhttprequest to "move" the image to the right location
  1678.   var hr = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1']
  1679.                      .createInstance(Components.interfaces.nsIXMLHttpRequest);
  1680.   hr.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
  1681.   hr.onreadystatechange = function (aEvent) {
  1682.     _logger.info("[piczoAPI].call(): hr.onreadystatechange for Moving URL: "+hr.readyState);
  1683.     if (listener) {
  1684.       if (hr.readyState == 4) {
  1685.         try {
  1686.           if (hr.status/100 == 2) {
  1687.             try {
  1688.               listener.onSuccess(hr, aURL);
  1689.             } catch (ex) {
  1690.               _logger.info(ex);
  1691.               listener.onError(null);
  1692.             }
  1693.           } else {
  1694.             // HTTP error
  1695.             listener.onError(null);
  1696.           }
  1697.         } catch (ex) {
  1698.           _logger.info(ex);
  1699.           var err = {
  1700.             errorCode: Components.interfaces.flockIError.PHOTOSERVICE_UNAVAILABLE,
  1701.             errorString: "Request did not complete."
  1702.           };
  1703.           listener.onError(err);
  1704.         }
  1705.       }
  1706.     }
  1707.   };
  1708.   hr.open("GET", aURL, true);
  1709.   hr.send(null);
  1710. }
  1711.  
  1712. piczoAPI.prototype.getFriends =
  1713. function piczoAPI_getFriends(aListener)
  1714. {
  1715.   _logger.info("[piczoAPI].getFriends()");
  1716.   var api = this;
  1717.  
  1718.   var getNodeSubvalue = function (aNode) {
  1719.     var longest = "";
  1720.     for (var i = 0; i < aNode.childNodes.length; i++) {
  1721.       var item = aNode.childNodes.item(i);
  1722.       if (item.nodeValue.length > longest.length) {
  1723.         longest = item.nodeValue;
  1724.       }
  1725.     }
  1726.     return longest;
  1727.   };
  1728.  
  1729.   var friendsListener = {
  1730.     onSuccess: function (aSubject, aTopic) {
  1731.       _logger.info("[piczoAPI].getFriends() friendsListener.onSuccess()");
  1732.       _logger.info(aSubject.responseText);
  1733.       var replyEl = aSubject.responseXML.getElementsByTagName("lookup.reply").item(0);
  1734.       var friendsEl = replyEl.getElementsByTagName("friends").item(0);
  1735.       var friendEls = friendsEl.getElementsByTagName("friend");
  1736.       var friends = [];
  1737.       for (var i = 0; i < friendEls.length; i++) {
  1738.         var friendEl = friendEls.item(i);
  1739.         var primaryProfEl = friendEl.getElementsByTagName("primary-profile").item(0);
  1740.         var thumbEl = primaryProfEl.getElementsByTagName("thumbnail").item(0);
  1741.         var websiteEl = primaryProfEl.getElementsByTagName("website-link").item(0);
  1742.         friends.push({
  1743.           id: friendEl.getAttribute("guid"),
  1744.           name: getNodeSubvalue(friendEl.getElementsByTagName("name").item(0)),
  1745.           avatarURL: getNodeSubvalue(thumbEl.getElementsByTagName("uri").item(0)),
  1746.           URL: getNodeSubvalue(websiteEl.getElementsByTagName("uri").item(0))
  1747.         });
  1748.       }
  1749.       aListener.onSuccess(friends, aTopic);
  1750.     },
  1751.     onError: function (aSubject, aTopic, aFlockError) {
  1752.       _logger.info("[piczoAPI].getFriends() friendsListener.onError()");
  1753.       if (api.handleStaleToken(aSubject)) {
  1754.         // Resubmit the request with the new token
  1755.         callGetFriends();
  1756.       } else {
  1757.         aListener.onError(aFlockError);
  1758.       }
  1759.     }
  1760.   };
  1761.  
  1762.   var callGetFriends = function () {
  1763.     var url = gStrings["api-friends-URL"];
  1764.     var headers = {
  1765.       "ts": api.getTS(),
  1766.       "sn": api.mSession.token,
  1767.     };
  1768.     api.call("get", url, headers, null, friendsListener, true);
  1769.   };
  1770.  
  1771.   callGetFriends();
  1772. }
  1773.  
  1774. // ========== END piczoAPI class ==========
  1775.  
  1776.  
  1777.  
  1778. // ==============================================
  1779. // ========== BEGIN piczoAccount class ==========
  1780. // ==============================================
  1781.  
  1782. function piczoAccount()
  1783. {
  1784.   _logger.info("piczoAccount CONSTRUCTOR");
  1785.   this.acUtils = Components.classes["@flock.com/account-utils;1"]
  1786.                            .getService(Components.interfaces.flockIAccountUtils);
  1787.   this._coop = Components.classes["@flock.com/singleton;1"]
  1788.                          .getService(Components.interfaces.flockISingleton)
  1789.                          .getSingleton("chrome://browser/content/flock/common/load-faves-coop.js")
  1790.                          .wrappedJSObject;
  1791.   this.mAPI = gAPI;
  1792.   this._ctk = {
  1793.     interfaces: [
  1794.       "nsISupports",
  1795.       "flockIWebServiceAccount",
  1796.       "flockISocialWebServiceAccount",
  1797.       "flockIMediaWebServiceAccount",
  1798.       "flockIMediaUploadAccount",
  1799.     ],
  1800.   };
  1801.   getCompTK().addAllInterfaces(this);
  1802.  
  1803.   _logger.info("piczoAccount Constructor End.");
  1804. }
  1805.  
  1806. piczoAccount.prototype.urn = null;
  1807.  
  1808. // BEGIN flockIWebServiceAccount interface
  1809. piczoAccount.prototype.login =
  1810. function piczoAccount_login(aListener)
  1811. {
  1812.   var account = this;
  1813.   var c_acct = account._coop.get(account.urn);
  1814.   var loginListener = {
  1815.     onSuccess: function (aSubject, aTopic) {
  1816.       account.acUtils.ensureOnlyAuthenticatedAccount(account.urn);
  1817.       c_acct.piczo_guid = account.mAPI.mSession.userGUID;
  1818.       if (aListener) { aListener.onSuccess(aSubject, aTopic); }
  1819.     },
  1820.     onError: function (aSubject, aTopic, aError) {
  1821.       c_acct.isAuthenticated = false;
  1822.       if (aListener) { aListener.onError(aSubject, aTopic, aError); }
  1823.     }
  1824.   };
  1825.   var pw = this.acUtils.getPassword("urn:piczo:service"+':'+c_acct.accountId);
  1826.   this.mAPI.login(pw.user, pw.password, loginListener);
  1827. }
  1828.  
  1829. piczoAccount.prototype.logout =
  1830. function piczoAccount_logout(aListener)
  1831. {
  1832.   _logger.info("{flockIWebServiceAccount}.logout()");
  1833.   var c_acct = this._coop.get(this.urn);
  1834.   if (c_acct.isAuthenticated) {
  1835.     try {
  1836.       this.mAPI.logout();
  1837.     } catch (ex) {
  1838.       _logger.warn("API logout call failed with error: "+ex);
  1839.     }
  1840.     Cc[CONTRACT_ID].getService(Ci.flockIWebService).logout();
  1841.     c_acct.isAuthenticated = false;
  1842.   }
  1843. }
  1844.  
  1845. piczoAccount.prototype.activate =
  1846. function piczoAccount_activate(aListener)
  1847. {
  1848.   _logger.info("{flockIWebServiceAccount}.activate()");
  1849.   var c_acct = this._coop.get(this.urn);
  1850.   //c_acct.isTransient = false;
  1851.   var account = this;
  1852.   var loginListener = {
  1853.     onSuccess: function (aSubject, aTopic) {
  1854.       _logger.info(".activate() loginListener.onSuccess()");
  1855.       // XXX TODO FIXME: API login
  1856.       if (aListener) aListener.onSuccess(account, "accountAuthorized");
  1857.       c_acct.isPollable = true;
  1858.       account.acUtils.ensureOnlyAuthenticatedAccount(account.urn);
  1859.     },
  1860.     onError: function (aSubject, aTopic, aError) {
  1861.       _logger.info(".activate() loginListener.onError()");
  1862.       c_acct.isAuthenticated = true;
  1863.     }
  1864.   };
  1865.   //this.acUtils.makeTempPasswordPermanent(this.service.urn+':'+c_acct.accountId);
  1866.   this.login(loginListener);
  1867.   // Assume login will succeed.  Listener will handle failures.
  1868.   c_acct.isAuthenticated = true;
  1869. }
  1870. // END flockIWebServiceAccount interface
  1871.  
  1872.  
  1873. // BEGIN flockISocialWebServiceAccount interface
  1874. piczoAccount.prototype.browseFriends =
  1875. function piczoAccount_browseFriends(aFriendURN, aListener)
  1876. {
  1877.   _logger.info("{flockISocialWebServiceAccount}.browseFriends('"+aFriendURN+"')");
  1878.   // XXX TODO FIXME: implement
  1879. }
  1880. // END flockISocialWebServiceAccount interface
  1881.  
  1882. // ========== END piczoAccount class ==========
  1883.  
  1884.  
  1885.  
  1886. // =========================================
  1887. // ========== BEGIN XPCOM Support ==========
  1888. // =========================================
  1889.  
  1890. function createModule(aParams) {
  1891.   return {
  1892.     registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
  1893.       var aCompMgr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1894.       aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
  1895.                                         aParams.contractID, aFileSpec,
  1896.                                         aLocation, aType );
  1897.       var catMgr = Cc["@mozilla.org/categorymanager;1"]
  1898.         .getService(Ci.nsICategoryManager);
  1899.       if (!aParams.categories) { aParams.categories = []; }
  1900.       for (var i = 0; i < aParams.categories.length; i++) {
  1901.         var cat = aParams.categories[i];
  1902.         catMgr.addCategoryEntry( cat.category, cat.entry,
  1903.                                  cat.value, true, true ); 
  1904.       }
  1905.     },
  1906.     getClassObject: function (aCompMgr, aCID, aIID) {
  1907.       if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; }
  1908.       if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
  1909.       return { // Factory
  1910.         createInstance: function (aOuter, aIID) {
  1911.           if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; }
  1912.           var comp = new aParams.componentClass();
  1913.           if (aParams.implementationFunc) { aParams.implementationFunc(comp); }
  1914.           return comp.QueryInterface(aIID);
  1915.         }
  1916.       };
  1917.     },
  1918.     canUnload: function (aCompMgr) { return true; }
  1919.   };
  1920. }
  1921.  
  1922. // NS Module entrypoint
  1923. function NSGetModule(aCompMgr, aFileSpec) {
  1924.   return createModule({
  1925.     componentClass: piczoService,
  1926.     CID: CLASS_ID,
  1927.     contractID: CONTRACT_ID,
  1928.     componentName: CATEGORY_COMPONENT_NAME,
  1929.     implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); aComp.init(); },
  1930.     categories: [ 
  1931.       { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: CONTRACT_ID },
  1932.       { category: "flockIPhotoAPI", entry: CATEGORY_ENTRY_NAME, value: CONTRACT_ID } ,
  1933.       { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: CONTRACT_ID }
  1934.     ]
  1935.   });
  1936. }
  1937.  
  1938. // ========== END XPCOM Support ==========
  1939.